﻿namespace SaxParser
{
    using System;
    using System.Collections.Generic;
    using System.Xml;
    using System.IO;

    /// <summary>
    /// My Sax Parser
    /// </summary>
    public class MySaxParser
    {
        #region Main Logic

        public void Parse(string path)
        {
            try
            {
                string content = string.Empty;
                int contentPosition = 0;

                using (StreamReader reader = new StreamReader(path))
                {
                    content = reader.ReadToEnd();                    
                }
                content = content.Replace(Environment.NewLine, "");
                content = content.Replace("\t", "");
                contentPosition = content.IndexOf('<');
                while (contentPosition < content.Length - 1 && contentPosition != -1)
                {
                    switch (content[contentPosition + 1])
                    {
                        case '?':
                            {
                                contentPosition += 2;
                                if (content.IndexOf('>', contentPosition) == content.IndexOf('?', contentPosition) + 1)
                                {
                                    string declaration = content.Substring(contentPosition + 3, content.IndexOf("?>", contentPosition) - contentPosition - 3);

                                    if (OnDocumentDeclaration != null)
                                        OnDocumentDeclaration(this, new MyEventArgs("Find document declaration:", declaration));
                                }

                                break;
                            }
                        case '!':
                            {
                                if (content.Substring(contentPosition + 2, 2).Equals("--"))
                                {
                                    contentPosition += 4;
                                    string comment = content.Substring(contentPosition, content.IndexOf("-->", contentPosition) - contentPosition);

                                    if (OnComment != null)
                                        OnComment(this, new MyEventArgs("Find comment. Value:", comment));
                                }
                                break;
                            }
                        case '/':
                            {
                                contentPosition += 2;
                                string endTag = content.Substring(contentPosition, content.IndexOf('>', contentPosition) - contentPosition);

                                if (OnElementEnd != null)
                                    OnElementEnd(this, new MyEventArgs("End of tag. Name:", endTag));

                                break;
                            }
                        default:
                            {
                                List<KeyValuePair<string, string>> attributes = new List<KeyValuePair<string, string>>();
                                contentPosition += 1;
                                string inTag = content.Substring(contentPosition, content.IndexOf('>', contentPosition) - contentPosition);
                                string tagName = inTag;

                                if (inTag.IndexOf('=') != -1)
                                {
                                    tagName = inTag.Substring(0, inTag.IndexOf(' '));
                                    int attributePosition = inTag.IndexOf(' ') + 1;
                                    do
                                    {
                                        string paramName = inTag.Substring(attributePosition, inTag.IndexOf('=', attributePosition) - attributePosition);
                                        string paramValue = string.Empty;
                                        attributePosition = inTag.IndexOf('=', attributePosition) + 1;

                                        if (inTag.IndexOf(' ', attributePosition) == -1)
                                        {
                                            paramValue = inTag.Substring(attributePosition, inTag.Length - attributePosition);
                                            attributePosition = inTag.Length;
                                        }
                                        else
                                        {
                                            paramValue = inTag.Substring(attributePosition, inTag.IndexOf(' ', attributePosition) - attributePosition);
                                            attributePosition = inTag.IndexOf(' ', attributePosition) + 1;
                                        }

                                        attributes.Add(new KeyValuePair<string, string>(paramName, paramValue));
                                    } while (attributePosition < inTag.Length);
                                }

                                if (OnElementStart != null)
                                    OnElementStart(this, new ElementStartEventArgs(tagName, attributes));

                                contentPosition += inTag.Length + 1;

                                if (!content[contentPosition].Equals('<'))
                                {
                                    if (OnText != null)
                                        OnText(this, new MyEventArgs("Tag value:", content.Substring(contentPosition, content.IndexOf('<', contentPosition) - contentPosition)));
                                }

                                break;
                            }
                    }

                    contentPosition = content.IndexOf('<', contentPosition);

                }
            }
            catch (Exception exp)
            {
                if (OnError != null)
                    OnError(this, new MyEventArgs("Invalid input xml document. Error:", exp.Message));
            }
        }

        #endregion

        #region Delegates

        public delegate void ElenemtStartEventHandler(object sender, ElementStartEventArgs e);
        public delegate void ElenemtEndEventHandler(object sender, MyEventArgs e);
        public delegate void TextEventHandler(object sender, MyEventArgs e);
        public delegate void DocumentDeclarationEventHandler(object sender, MyEventArgs e);
        public delegate void CommentEventHandler(object sender, MyEventArgs e);
        public delegate void ErrorEventHandler(object sender, MyEventArgs e);

        #endregion

        #region Events

        public event ElenemtEndEventHandler OnElementEnd;
        public event ElenemtStartEventHandler OnElementStart;
        public event TextEventHandler OnText;
        public event DocumentDeclarationEventHandler OnDocumentDeclaration;
        public event CommentEventHandler OnComment;
        public event ErrorEventHandler OnError;

        #endregion
    }
}
